package com.example.autumn.aop;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;

/**
 * @author liuzhiyong
 * @date 2023/10/27
 * description: 代理解析器
 */
public class ProxyResolver {

    final Logger logger = LoggerFactory.getLogger(getClass());

    // 实现动态代理CGLIB已经停止维护
    final ByteBuddy byteBuddy = new ByteBuddy();

    private static ProxyResolver INSTANCE = null;

    public static ProxyResolver getInstance() {
        if (INSTANCE == null) {
            return new ProxyResolver();
        }
        return INSTANCE;
    }

    /**
     * 创建对象
     * 实现了使用代理对象, 调用代理对象的方法, 代理对象的方法调用自定义的处理器, 里面可以通过反射执行原方法, 也可以在执行前后进行函数的增强
     *
     * @param bean 原始的Bean, 目标Bean
     * @param handler 处理器, 调用需要在代理对象里面执行的方法
     * @return {@link T } 代理对象
     * @author liuzhiyong
     * @date 2023/10/27
     */
    @SuppressWarnings("unchecked")
    public <T> T createProxy(T bean, InvocationHandler handler) {
        // 目标Bean的class
        Class<?> targetClass  = bean.getClass();
        logger.atDebug().log("create proxy for bean {} @{}", targetClass.getName(), Integer.toHexString(bean.hashCode()));
        // 动态创建Proxy的Class
        Class<?> proxyClass = this.byteBuddy
                // 子类用默认的无参构造方法
                .subclass(targetClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
                // 拦截所有public方法
                .method(ElementMatchers.isPublic())
                // 使用lambda表达式的形式, 新建拦截器, 拦截上面执行的方法, 在里面调用handler的invoke函数,
                // handler的invoke函数里面, 会通过反射执行目标Bean的函数, 可以通过自定义的标识, 来识别,是不是需要拦截的方法, 不是的话可以通过反射执行原函数, 是的话, 可以在调用原函数前后进行增强
                .intercept(InvocationHandlerAdapter.of((proxy, method, args) -> handler.invoke(bean, method, args)))
                // 生成字节码
                .make()
                // 加载字节码
                .load(targetClass.getClassLoader()).getLoaded();
        Object proxy;
        try {
            proxy = proxyClass.getConstructor().newInstance();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return (T) proxy;
    }

}
